﻿#region << 版 本 注 释 >>
/*----------------------------------------------------------------
 * 版权所有 (c) 2022 北京超维景生物科技有限公司 保留所有权利。
 * CLR版本：4.0.30319.42000
 * 文件名：Kernel
 * 
 * 创建者：huangyang
 * 电子邮箱：huangyang@tvscope.cn
 * 创建时间：2023/2/8 13:12:06
 * 版本：V1.0.0
 * 描述：
 *
 * ----------------------------------------------------------------
 * 修改人：
 * 时间：
 * 修改说明：
 *
 * 版本：V1.0.1
 *----------------------------------------------------------------*/
#endregion << 版 本 注 释 >>

using System;
using System.Collections.Generic;
using System.Drawing.Imaging;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using ImageK.Gui;
using ImageK.Java;
using ImageK.Plugin;
using ImageK.Plugin.Frame;
using ImageK.Process;
using ImageK.Util;

namespace ImageK.IO
{
    /// <summary>
    /// Opens tiff (and tiff stacks), dicom, fits, pgm, jpeg, bmp or
    /// gif images, and look-up tables, using a file open dialog or a path.
    /// Calls HandleExtraFileTypes plugin if the file type is unrecognised.
    /// </summary>
    public class Opener
    {
        public const int UNKNOWN = 0, TIFF = 1, DICOM = 2, FITS = 3, PGM = 4, JPEG = 5,
            GIF = 6, LUT = 7, BMP = 8, ZIP = 9, JAVA_OR_TEXT = 10, ROI = 11, TEXT = 12, PNG = 13,
            TIFF_AND_DICOM = 14, CUSTOM = 15, AVI = 16, OJJ = 17, TABLE = 18, RAW = 19; // don't forget to also update 'types'
        public static string[] types = {"unknown","tif","dcm","fits","pgm",
            "jpg","gif","lut","bmp","zip","java/txt","roi","txt","png","t&d","custom","ojj","table","raw"};
        private int _fileType;
        private bool _error;
        private bool _isRGB48;
        private bool _silentMode;

        private static bool _openUsingPlugins;
        private static bool _bioformats;
        private string _url;
        private bool _useHandleExtraFileTypes;

        public Opener()
        {
        }

        /**
		 * Displays a file open dialog box and then opens the tiff, dicom, 
		 * fits, pgm, jpeg, bmp, gif, lut, roi, or text file selected by 
		 * the user. Displays an error message if the selected file is not
		 * in a supported format. This is the method that
		 * ImageJ's File/Open command uses to open files.
		 * @see ij.IJ#open
		 * @see ij.IJ#open(String)
		 * @see ij.IJ#openImage
		 * @see ij.IJ#openImage(String)
		*/
        public void open()
        {
            OpenDialog od = new OpenDialog("Open", "");
            var dir = od.GetDirectory();
            if (string.IsNullOrEmpty(dir))
                return;
            string directory = dir.TrimEnd('/').TrimEnd('\\');
            string name = od.GetFileName();
            if (name != null)
            {
                string path = Path.Combine(directory, name);
                _error = false;
                open(path);
                if (!_error)
                {
                    Menus.addOpenRecentItem(path);
                }
                    
            }
        }

        /// <summary>
        /// Opens and displays the specified tiff, dicom, fits, pgm, jpeg, 
        /// bmp, gif, lut, roi, or text file.Displays an error message if 
        /// the file is not in a supported format.
        /// @see ij.IJ#open(String)
        /// @see ij.IJ#openImage(String)
        /// </summary>
        /// <param name="path"></param>
        public void open(string path)
        {
            bool isURL = path.Contains("://") || path.Contains("file:/");
            if (isURL && IsText(path))
            {
                OpenTextURL(path);
                return;
            }
            if (path.EndsWith(".jar") || path.EndsWith(".class"))
            {
                //todo:
                //(new PluginInstaller()).install(path);
                return;
            }
            path = MakeFullPath(path);
            if (!_silentMode)
                IJ.ShowStatus("Opening: " + path);
            long start = DateTime.Now.Ticks;
            ImagePlus imp = null;
            if (path.EndsWith(".txt"))
                this._fileType = JAVA_OR_TEXT;
            else
            {
                _useHandleExtraFileTypes = true;
                imp = openImage(path);
            }
            if (imp == null && isURL)
                return;
            if (imp != null)
            {
                WindowManager.CheckForDuplicateName = true;
                if (_isRGB48)
                {
                    //todo:
                    // openRGB48(imp);
                }
                else
                    imp.Show(getLoadRate(start, imp));
            }
            else
            {
                //todo:
                // switch (this.fileType)
                // {
                //     case LUT:
                //         imp = (ImagePlus)IJ.runPlugIn("ij.plugin.LutLoader", path);
                //         if (imp.getWidth() != 0)
                //             imp.show();
                //         break;
                //     case ROI:
                //         IJ.runPlugIn("ij.plugin.RoiReader", path);
                //         break;
                //     case JAVA_OR_TEXT:
                //     case TEXT:
                //         if (IJ.altKeyDown())
                //         { // open in TextWindow if alt key down
                //             new TextWindow(path, 400, 450);
                //             IJ.setKeyUp(KeyEvent.VK_ALT);
                //             break;
                //         }
                //         File file = new File(path);
                //         int maxSize = 250000;
                //         long size = file.length();
                //         if (size >= 28000)
                //         {
                //             String osName = System.getProperty("os.name");
                //             if (osName.equals("Windows 95") || osName.equals("Windows 98") || osName.equals("Windows Me"))
                //                 maxSize = 60000;
                //         }
                //         if (size < maxSize)
                //         {
                //             Editor ed = new Editor(path);
                //             if (ed != null) ed.open(getDir(path), getName(path));
                //         }
                //         else
                //             new TextWindow(path, 400, 450);
                //         break;
                //     case OJJ:  // ObjectJ project
                //         IJ.runPlugIn("ObjectJ_", path);
                //         break;
                //     case TABLE:
                //         openTable(path);
                //         break;
                //     case RAW:
                //         IJ.runPlugIn("ij.plugin.Raw", path);
                //         break;
                //     case UNKNOWN:
                //         File f = new File(path);
                //         String msg = (f.exists()) ?
                //             "Format not supported or reader plugin not found:"
                //             : "File not found:";
                //         if (path != null)
                //         {
                //             if (path.length() > 64)
                //                 path = (new File(path)).getName();
                //             if (path.length() <= 64)
                //                 msg += " \n" + path;
                //         }
                //         if (openUsingPlugins && msg.length() > 20)
                //             msg += "\n \nNOTE: The \"OpenUsingPlugins\" option is set.";
                //         IJ.wait(IJ.isMacro() ? 500 : 100); // work around for OS X thread deadlock problem
                //         IJ.error("Opener", msg);
                //         error = true;
                //         break;
                // }
            }
        }

        /// <summary>
        /// Opens, but does not display, the specified image file
        /// and returns an ImagePlus object object if successful,
        /// or returns null if the file is not in a supported format
        /// or is not found. Displays a file open dialog if 'path'
        /// is null or an empty string.
        /// @see ij.IJ#openImage(String)
        /// @see ij.IJ#openImage
        /// @see #openUsingBioFormats
        /// </summary>
        public ImagePlus openImage(string path)
        {
            if (string.IsNullOrEmpty(path))
                path = GetPath();
            if (path == null) return null;
            ImagePlus img = null;
            if (path.Contains("://") || path.Contains("file:/")) // path is a URL
                img = OpenURL(path);
            else
                img = openImage(PathUtil.GetDir(path), PathUtil.GetName(path));
            return img;
        }

        /**
         * Open the nth image of the specified tiff stack.
         * @see ij.IJ#openImage(String,int)
        */
        public ImagePlus openImage(string path, int n)
        {
            if (path == null || path.Equals(""))
                path = GetPath();
            if (path == null) return null;
            int type = getFileType(path);
            if (type != TIFF)
                throw new ArgumentException("Opener: TIFF file required");
            return openTiff(path, n);
        }

        public static string getLoadRate(double time, ImagePlus imp)
        {
            time = (DateTime.Now.Ticks - time) / 1000.0;
            double mb = imp.getWidth() * imp.getHeight() * imp.getStackSize();
            int bits = imp.getBitDepth();
            if (bits == 16)
                mb *= 2;
            else if (bits == 24 || bits == 32)
                mb *= 4;
            mb /= 1024 * 1024;
            double rate = mb / time;
            int digits = rate < 100.0 ? 1 : 0;
            return "" + IJ.d2s(time, 2) + " seconds (" + IJ.d2s(mb / time, digits) + " MB/sec)";
        }

        string GetPath()
        {
            OpenDialog od = new OpenDialog("Open", "");
            String dir = od.GetDirectory();
            String name = od.GetFileName();
            if (name == null)
                return null;
            else
                return dir + name;
        }

        /// <summary>
        /// Attempts to open the specified url as a tiff, zip compressed tiff, 
        /// dicom, gif or jpeg. Tiff file names must end in ".tif", ZIP file names 
        /// must end in ".zip" and dicom file names must end in ".dcm". Returns an 
        /// ImagePlus object if successful.
        /// @see ij.IJ#openImage(String)
        /// </summary>
        public ImagePlus OpenURL(string url)
        {
            //todo:
            return null;
        }

        public static string MakeFullPath(string path)
        {
            if (string.IsNullOrEmpty(path))
                return path;
            if (!IsFullPath(path))
            {
                path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, path);
            }
            return path;
        }

        public static bool IsFullPath(String path)
        {
            if (string.IsNullOrEmpty(path))
                return false;
            else
                return path.StartsWith("/") || path.StartsWith("\\") || path.Contains(":\\") || path.Contains(":/") || path.Contains("://");
        }

        private bool IsText(String path)
        {
            if (path.EndsWith(".txt") || path.EndsWith(".ijm") || path.EndsWith(".java")
                || path.EndsWith(".js") || path.EndsWith(".html") || path.EndsWith(".htm")
                || path.EndsWith(".bsh") || path.EndsWith(".py") || path.EndsWith("/"))
                return true;
            int lastSlash = path.LastIndexOf("/");
            if (lastSlash == -1) lastSlash = 0;
            int lastDot = path.LastIndexOf(".");
            if (lastDot == -1 || lastDot < lastSlash || (path.Length - lastDot) > 6)
                return true;  // no extension
            else
                return false;
        }

        /** Opens the specified file and adds it to the File/Open Recent menu.
        Returns true if the file was opened successfully.  */
        public bool openAndAddToRecent(string path)
        {
            open(path);
            if (!_error)
                Menus.addOpenRecentItem(path);
            return !_error;
        }

        /// <summary>
        /// Attempts to open the specified file as a tiff, bmp, dicom, fits,
        /// pgm, gif or jpeg image. Returns an ImagePlus object if successful.
        /// Modified by Gregory Jefferis to call HandleExtraFileTypes plugin if 
        /// the file type is unrecognised.
        /// @see ij.IJ#openImage(String)
        /// </summary>
        public ImagePlus openImage(string directory, string name)
        {
            ImagePlus imp;
            FileOpener.SetSilentMode(_silentMode);
            if (directory.Length > 0 && !(directory.EndsWith("/") || directory.EndsWith("\\")))
                directory += Prefs.separator;
            OpenDialog.setLastDirectory(directory);
            OpenDialog.setLastName(name);
            string path = directory + name;
            this._fileType = getFileType(path);

            // if (IJ.debugMode) IJ.log("openImage: \"" + types[this.fileType] + "\", " + path);

            switch (this._fileType)
            {
                case TIFF:
                    imp = openTiff(directory, name);
                    return imp;

                //todo:其他类型
                // case DICOM:
                // case TIFF_AND_DICOM:
                //     imp = (ImagePlus)IJ.runPlugIn("ij.plugin.DICOM", path);
                //     if (imp.getWidth() != 0) return imp; else return null;
                // case FITS:
                //     imp = (ImagePlus)IJ.runPlugIn("ij.plugin.FITS_Reader", path);
                //     if (imp.getWidth() != 0) return imp; else return null;
                // case PGM:
                //     imp = (ImagePlus)IJ.runPlugIn("ij.plugin.PGM_Reader", path);
                //     if (imp.getWidth() != 0)
                //     {
                //         if (imp.getStackSize() == 3 && imp.getBitDepth() == 16)
                //             imp = new CompositeImage(imp, IJ.COMPOSITE);
                //         return imp;
                //     }
                //     else
                //         return null;
                case JPEG:
                    imp = openJpegOrGif(directory, name);
                    if (imp != null && imp.getWidth() != 0) return imp; else return null;
                // case GIF:
                //     imp = (ImagePlus)IJ.runPlugIn("ij.plugin.GIF_Reader", path);
                //     if (imp != null && imp.getWidth() != 0) return imp; else return null;
                case PNG:
                    imp = openUsingImageIO(directory + name);
                    if (imp != null && imp.getWidth() != 0) return imp; else return null;
                // case BMP:
                //     imp = (ImagePlus)IJ.runPlugIn("ij.plugin.BMP_Reader", path);
                //     if (imp.getWidth() != 0) return imp; else return null;
                // case ZIP:
                //     return openZip(path);
                // case AVI:
                //     AVI_Reader reader = new AVI_Reader();
                //     reader.setVirtual(true);
                //     reader.displayDialog(!IJ.macroRunning());
                //     reader.run(path);
                //     return reader.getImagePlus();
                // case JAVA_OR_TEXT:
                //     if (name.endsWith(".txt"))
                //         return openTextImage(directory, name);
                //     else
                //         return null;
                // case UNKNOWN:
                // case TEXT:
                //     imp = null;
                //     if (name.endsWith(".lsm"))
                //         useHandleExtraFileTypes = true; // use LSM_Reader to opem .lsm files
                //     if (!useHandleExtraFileTypes)
                //         imp = openUsingBioFormats(path);
                //     useHandleExtraFileTypes = false;
                //     if (imp != null)
                //         return imp;
                //     else
                //         return openUsingHandleExtraFileTypes(path);
                default:
                    return null;
            }

        }

        public ImagePlus openTempImage(String directory, String name)
        {
            ImagePlus imp = openImage(directory, name);
            if (imp!=null)
                imp.setTemporary();
            return imp;
        }

        // Call HandleExtraFileTypes plugin to see if it can handle unknown formats
        // or files in TIFF format that the built in reader is unable to open.
        private ImagePlus openUsingHandleExtraFileTypes(string path)
        {
            if (!File.Exists(path))
            {
                return null;
            }
            int nImages = WindowManager.getImageCount();
            int[] wrap = new int[] { this._fileType };
            ImagePlus imp = openWithHandleExtraFileTypes(path, wrap);
            if (imp != null && imp.getNChannels() > 1)
                imp = new CompositeImage(imp, IJ.COLOR);
            this._fileType = wrap[0];
            if (imp == null && (this._fileType == UNKNOWN || this._fileType == TIFF) && WindowManager.getImageCount() == nImages)
                IJ.error("Opener", "Unsupported format or file not found:\n" + path);
            return imp;
        }

        string getPath()
        {
            OpenDialog od = new OpenDialog("Open", "");
            string dir = od.GetDirectory();
            string name = od.GetFileName();
            if (name==null)
                return null;
            else
                return dir+name;
        }

        /** Opens the specified text file as a float image. */
        // public ImagePlus openTextImage(String dir, String name)
        // {
        //     String path = dir+name;
        //     TextReader tr = new TextReader();
        //     ImageProcessor ip = tr.open(path);
        //     return ip!=null ? new ImagePlus(name, ip) : null;
        // }

        /**
         * Attempts to open the specified url as a tiff, zip compressed tiff, 
         * dicom, gif or jpeg. Tiff file names must end in ".tif", ZIP file names 
         * must end in ".zip" and dicom file names must end in ".dcm". Returns an 
         * ImagePlus object if successful.
         * @see ij.IJ#openImage(String)
        */
        // public ImagePlus openURL(String url)
        // {
        //     url = updateUrl(url);
        //     if (IJ.debugMode) IJ.log("OpenURL: "+url);
        //     ImagePlus imp = openCachedImage(url);
        //     if (imp!=null)
        //         return imp;
        //     try
        //     {
        //         String name = "";
        //         int index = url.lastIndexOf('/');
        //         if (index==-1)
        //             index = url.lastIndexOf('\\');
        //         if (index>0)
        //             name = url.substring(index+1);
        //         else
        //             throw new MalformedURLException("Invalid URL: "+url);
        //         if (url.indexOf(" ")!=-1)
        //             url = url.replaceAll(" ", "%20");
        //         URL u = new URL(url);
        //         IJ.showStatus(""+url);
        //         String lurl = url.toLowerCase(Locale.US);
        //         if (lurl.endsWith(".tif"))
        //         {
        //             this.url = url;
        //             imp = openTiff(u.openStream(), name);
        //         }
        //         else if (lurl.endsWith(".zip"))
        //             imp = openZipUsingUrl(u);
        //         else if (lurl.endsWith(".jpg") || lurl.endsWith(".jpeg") || lurl.endsWith(".gif"))
        //             imp = openJpegOrGifUsingURL(name, u);
        //         else if (lurl.endsWith(".dcm") || lurl.endsWith(".ima"))
        //         {
        //             imp = (ImagePlus)IJ.runPlugIn("ij.plugin.DICOM", url);
        //             if (imp!=null && imp.getWidth()==0) imp = null;
        //         }
        //         else if (lurl.endsWith(".png"))
        //             imp = openPngUsingURL(name, u);
        //         else
        //         {
        //             URLConnection uc = u.openConnection();
        //             String type = uc.getContentType();
        //             if (type!=null && (type.equals("image/jpeg")||type.equals("image/gif")))
        //                 imp = openJpegOrGifUsingURL(name, u);
        //             else if (type!=null && type.equals("image/png"))
        //                 imp = openPngUsingURL(name, u);
        //             else
        //                 imp = openWithHandleExtraFileTypes(url, new int[] { 0 });
        //         }
        //         IJ.showStatus("");
        //         return imp;
        //     }
        //     catch (Exception e)
        //     {
        //         String msg = e.getMessage();
        //         if (msg==null || msg.equals(""))
        //             msg = "" + e;
        //         msg += "\n"+url;
        //         IJ.error("Open URL", msg);
        //         return null;
        //     }
        // }

        /** Can't open imagej.nih.gov URLs due to encryption so redirect to imagej.net mirror. */
        public static String updateUrl(String url)
        {
            if (url==null || !url.Contains("nih.gov"))
                return url;
            if (IJ.isJava18())
                url = url.Replace("http:", "https:");
            else
            {
                url = url.Replace("imagej.nih.gov/ij", "imagej.net");
                url = url.Replace("rsb.info.nih.gov/ij", "imagej.net");
                url = url.Replace("rsbweb.nih.gov/ij", "imagej.net");
            }
            return url;
        }

        private ImagePlus openCachedImage(String url)
        {
            //todo:
            throw new NotImplementedException();
            // if (url==null || !url.Contains("/images"))
            //     return null;
            // String ijDir = IJ.getDirectory("imagej");
            // if (ijDir==null)
            //     return null;
            // int slash = url.LastIndexOf('/');
            // // File file = new File());
            //
            // System.IO.FileInfo file = new System.IO.FileInfo(Path.Combine(ijDir + "samples", url.Substring(slash + 1)));
            // if (!file.Exists)
            //     return null;
            // if (url.EndsWith(".gif"))  // ij.plugin.GIF_Reader does not correctly handle inverting LUTs
            //     return openJpegOrGif(file.getParent()+File.separator, file.getName());
            // return IJ.openImage(file.getPath());
        }

        /** Used by open() and IJ.open() to open text URLs. */
        void openTextURL(String url)
        {
            //todo:
            // if (url.endsWith(".pdf")||url.endsWith(".zip"))
            //     return;
            // String text = IJ.openUrlAsString(url);
            // if (text!=null && text.startsWith("<Error: "))
            // {
            //     IJ.error("Open Text URL", text);
            //     return;
            // }
            // String name = url.substring(7);
            // int index = name.lastIndexOf("/");
            // int len = name.length();
            // if (index==len-1)
            //     name = name.substring(0, len-1);
            // else if (index!=-1 && index<len-1)
            //     name = name.substring(index+1);
            // name = name.replaceAll("%20", " ");
            // Editor ed = new Editor(name);
            // ed.setSize(600, 300);
            // ed.create(name, text);
            // IJ.showStatus("");
        }

        // public ImagePlus openWithHandleExtraFileTypes(String path, int[] fileTypes)
        // {
        //     ImagePlus imp = null;
        //     if (path.EndsWith(".db"))
        //     {
        //         // skip hidden Thumbs.db files on Windows
        //         fileTypes[0] = CUSTOM;
        //         return null;
        //     }
        //     try
        //     {
        //         imp = (ImagePlus)IJ.runPlugIn("HandleExtraFileTypes", path);
        //     }
        //     catch (Exception e)
        //     {
        //         if (IJ.debugMode) IJ.log("openWithHandleExtraFileTypes:\n"+path+"\n"+e);
        //         imp = null;
        //     }
        //     if (imp==null)
        //         return null;
        //     FileInfo fi = imp.getOriginalFileInfo();
        //     if (fi==null)
        //     {
        //         fi = new FileInfo();
        //         fi.width = imp.getWidth();
        //         fi.height = imp.getHeight();
        //         fi.directory = getDir(path);
        //         fi.fileName = getName(path);
        //         imp.setFileInfo(fi);
        //     }
        //     if (imp.getWidth()>0 && imp.getHeight()>0)
        //     {
        //         fileTypes[0] = CUSTOM;
        //         return imp;
        //     }
        //     else
        //     {
        //         if (imp.getWidth()==-1)
        //             fileTypes[0] = CUSTOM; // plugin opened image so don't display error
        //         return null;
        //     }
        // }

        /** Opens the ZIP compressed TIFF or DICOM at the specified URL. */
        // ImagePlus openZipUsingUrl(URL url) 
        // {
        //     URLConnection uc = url.openConnection();
        //     InputStream in = uc.getInputStream();
        //     ZipInputStream zis = new ZipInputStream(in);
        //     ZipEntry entry = zis.getNextEntry();
        //     if (entry==null) {
        //         zis.close();
        //         return null;
        //     }
        //     String name = entry.getName();
        //         if (!(name.endsWith(".tif")||name.endsWith(".dcm")))
        //     throw new IOException("This ZIP archive does not appear to contain a .tif or .dcm file\n"+name);
        //     if (name.endsWith(".dcm"))
        //     return openDicomStack(zis, entry);
        //         else
        //         return openTiff(zis, name);
        // }

        //     ImagePlus openDicomStack(ZipInputStream zis, ZipEntry entry) throws IOException
        //     {
        //         ImagePlus imp = null;
        //
        //     int count = 0;
        //     ImageStack stack = null;
        //         while (true) {
        //         if (count>0) entry = zis.getNextEntry();
        //         if (entry==null) break;
        //         String name = entry.getName();
        //         ImagePlus imp2 = null;
        //         if (name.endsWith(".dcm")) {
        //             ByteArrayOutputStream out = new ByteArrayOutputStream();
        //             byte[] buf = new byte[4096];
        //             int len, byteCount = 0, progress = 0;
        //             while (true) {
        //                 len = zis.read(buf);
        //                 if (len<0) break;
        //                     out.write(buf, 0, len);
        //                 byteCount += len;
        //                 //IJ.showProgress((double)(byteCount%fileSize)/fileSize);
        //             }
        //             byte[] bytes = out.toByteArray();
        //                 out.close();
        //             InputStream is = new ByteArrayInputStream(bytes);
        //             DICOM dcm = new DICOM(is);
        //             dcm.run(name);
        //             imp2 = dcm;
        //                 is.close();
        //         }
        //         zis.closeEntry();
        //         if (imp2==null) continue;
        //         count++;
        //         String label = imp2.getTitle();
        //         String info = (String)imp2.getProperty("Info");
        //         if (info!=null) label += "\n" + info;
        //         if (count==1)
        //         {
        //             imp = imp2;
        //             imp.getStack().setSliceLabel(label, 1);
        //         }
        //         else
        //         {
        //             stack = imp.getStack();
        //             stack.addSlice(label, imp2.getProcessor());
        //             imp.setStack(stack);
        //         }
        //     }
        //     zis.close();
        //     IJ.showProgress(1.0);
        //     if (count==0)
        //     throw new IOException("This ZIP archive does not appear to contain any .dcm files");
        //         return imp;
        // }

        // ImagePlus openJpegOrGifUsingURL(String title, URL url)
        // {
        //     if (url==null) return null;
        //     Image img = Toolkit.getDefaultToolkit().createImage(url);
        //     if (img!=null)
        //     {
        //         ImagePlus imp = new ImagePlus(title, img);
        //         return imp;
        //     }
        //     else
        //         return null;
        // }

        // ImagePlus openPngUsingURL(String title, URL url)
        // {
        //     if (url==null)
        //         return null;
        //     Image img = null;
        //     try
        //     {
        //         InputStream in = url.openStream();
        //         img = ImageIO.read(in);
        //     }
        //     catch (FileNotFoundException e)
        //     {
        //         IJ.error("Open PNG Using URL", ""+e);
        //     }
        //     catch (IOException e)
        //     {
        //         IJ.handleException(e);
        //     }
        //     if (img!=null)
        //     {
        //         ImagePlus imp = new ImagePlus(title, img);
        //         return imp;
        //     }
        //     else
        //         return null;
        // }

        ImagePlus openJpegOrGif(string dir, string name)
        {
            ImagePlus imp = null;
            using FileStream fs = new FileStream(dir + name, FileMode.Open, FileAccess.Read);
            Image img = new Bitmap(fs);
            if (img!=null)
            {
                try
                {
                    imp = new ImagePlus(name, img);
                }
                catch (Exception e)
                {
                    IJ.error("Opener", e.Message+"\n(Note: ImageJ cannot open CMYK JPEGs)\n \n"+dir+name);
                    return null; // error loading image
                }
                if (imp.getType()==ImagePlus.COLOR_RGB)
                    convertGrayJpegTo8Bits(imp);
                FileInfo fi = new FileInfo();
                fi.fileFormat = FileInfo.GIF_OR_JPG;
                fi.fileName = name;
                fi.directory = dir;
                imp.setFileInfo(fi);
            }
            if (imp != null)
            {  // correct iPhone photo orientation (Norbert Vischer)
                String exifText = new ImageInfo().getExifData(imp);
                if (exifText != null)
                {
                    String[] lines = exifText.Split("\n");
                    for (int jj = 0; jj < lines.Length; jj++)
                    {
                        int orientationIndex = lines[jj].IndexOf("Orientation:");
                        int rotateIndex = lines[jj].IndexOf("Rotate");
                        if (orientationIndex >= 0 && rotateIndex >= 0)
                        {
                            String rest = lines[jj].Substring(rotateIndex);
                            rest = rest.Replace(")", " ");
                            String[] parts = rest.Split(" ");
                            if (parts.Length >= 2)
                            {
                                ImageProcessor ip = imp.getProcessor();
                                double angle = double.Parse(parts[1]);
                                ImageProcessor ip2 = null;
                                if (angle == 90)
                                    ip2 = ip.rotateRight();
                                else if (angle == 180)
                                {
                                    ip2 = ip.rotateRight();
                                    ip2 = ip2.rotateRight();
                                }
                                else if (angle == 270)
                                    ip2 = ip.rotateLeft();
                                if (ip2!=null)
                                    imp.setProcessor(ip2);
                                break;
                            }
                        }
                    }
                }
            }
            return imp;
        }

        public static ImagePlus openUsingImageIO(string path)
        {
            ImagePlus imp = null;
            Bitmap img = null;
            using FileStream f = new FileStream(path, FileMode.Open, FileAccess.Read);
            try
            {
                img = new Bitmap(f);
            }
            catch (Exception e)
            {
                IJ.error("Open Using ImageIO", ""+e);
            }
            if (img==null)
                return null;
            // if (IJ.debugMode) IJ.log("type="+img.getType()+", alpha="+img.getColorModel().hasAlpha()+", bands="+img.getSampleModel().getNumBands());
            // if (img.getColorModel().hasAlpha())
            // {
            //     int width = img.Width;
            //     int height = img.Height;
            //     Bitmap bi = new Bitmap(width, height, PixelFormat.Format32bppArgb);
            //     Graphics g = bi.getGraphics();
            //     Brush brush = new SolidBrush(Color.White);
            //     g.FillRectangle(brush,0, 0, width, height);
            //     g.DrawImage(img, 0, 0);
            //     img = bi;
            // }
            imp = new ImagePlus(f.Name, img);
            FileInfo fi = new FileInfo();
            fi.fileFormat = FileInfo.IMAGEIO;
            fi.fileName = f.Name;
            //todo:
            // string parent = f.getParent();
            // if (parent!=null)
            //     fi.directory = parent + File.separator;
            imp.setFileInfo(fi);
            return imp;
        }
        
        /** Converts the specified RGB image to 8-bits if the 3 channels are identical. */
        public static void convertGrayJpegTo8Bits(ImagePlus imp)
        {
            //todo:
            // ImageProcessor ip = imp.getProcessor();
            // if (ip.getBitDepth()==24 && ip.isGrayscale())
            // {
            //     IJ.ShowStatus("Converting to 8-bit grayscale");
            //     new ImageConverter(imp).convertToGray8();
            // }
        }

        /** Are all the images in this file the same size and type? */
        bool allSameSizeAndType(FileInfo[] info)
        {
            bool sameSizeAndType = true;
            bool contiguous = true;
            long startingOffset = info[0].getOffset();
            int size = info[0].width * info[0].height * info[0].getBytesPerPixel();
            for (int i = 1; i < info.Length; i++)
            {
                sameSizeAndType &= info[i].fileType == info[0].fileType
                                   && info[i].width == info[0].width
                                   && info[i].height == info[0].height;
                contiguous &= info[i].getOffset() == startingOffset + i * size;
            }
            if (contiguous && info[0].fileType != FileInfo.RGB48)
                info[0].nImages = info.Length;
            //if (IJ.debugMode) {
            //	IJ.log("sameSizeAndType: " + sameSizeAndType);
            //	IJ.log("contiguous: " + contiguous);
            //}
            return sameSizeAndType;
        }

        /** Attemps to open a tiff file as a stack. Returns 
        an ImagePlus object if successful. */
        public ImagePlus openTiffStack(FileInfo[] info)
        {
            if (info.Length > 1 && !allSameSizeAndType(info))
                return null;
            FileInfo fi = info[0];
            if (fi.nImages > 1)
                return new FileOpener(fi).openImage(); // open contiguous images as stack
            else
            {
                ColorModel cm = createColorModel(fi);
                ImageStack stack = new ImageStack(fi.width, fi.height, cm);
                object pixels = null;
                long skip = fi.getOffset();
                int imageSize = fi.width * fi.height * fi.getBytesPerPixel();
                if (info[0].fileType == FileInfo.GRAY12_UNSIGNED)
                {
                    imageSize = (int)(fi.width * fi.height * 1.5);
                    if ((imageSize & 1) == 1) imageSize++; // add 1 if odd
                }
                if (info[0].fileType == FileInfo.BITMAP)
                {
                    int scan = (int)Math.Ceiling(fi.width / 8.0);
                    imageSize = scan * fi.height;
                }
                long loc = 0L;
                int nChannels = 1;
                try
                {
                    InputStream rs = createInputStream(fi);
                    ImageReader reader = new ImageReader(fi);
                    IJ.resetEscape();
                    for (int i = 0; i < info.Length; i++)
                    {
                        nChannels = 1;
                        object[] channels = null;
                        if (!_silentMode)
                            IJ.ShowStatus("Reading: " + (i + 1) + "/" + info.Length);
                        if (IJ.escapePressed())
                        {
                            IJ.beep();
                            IJ.showProgress(1.0);
                            return null;
                        }
                        fi.stripOffsets = info[i].stripOffsets;
                        fi.stripLengths = info[i].stripLengths;
                        int bpp = info[i].getBytesPerPixel();
                        if (info[i].samplesPerPixel > 1 && !(bpp == 3 || bpp == 4 || bpp == 6))
                        {
                            nChannels = fi.samplesPerPixel;
                            channels = new object[nChannels];
                            for (int c = 0; c < nChannels; c++)
                            {
                                pixels = reader.readPixels(rs, c == 0 ? skip : 0L);
                                channels[c] = pixels;
                            }
                        }
                        else
                            pixels = reader.readPixels(rs, skip);
                        if (pixels == null && channels == null) break;
                        loc += imageSize * nChannels + skip;
                        if (i < (info.Length - 1))
                        {
                            skip = info[i + 1].getOffset() - loc;
                            if (info[i + 1].compression >= FileInfo.LZW) skip = 0;
                            if (skip < 0L)
                            {
                                IJ.error("Opener", "Unexpected image offset");
                                break;
                            }
                        }
                        if (fi.fileType == FileInfo.RGB48)
                        {
                            object[] pixels2 = (object[])pixels;
                            stack.addSlice(null, pixels2[0]);
                            stack.addSlice(null, pixels2[1]);
                            stack.addSlice(null, pixels2[2]);
                            _isRGB48 = true;
                        }
                        else if (nChannels > 1)
                        {
                            for (int c = 0; c < nChannels; c++)
                            {
                                if (channels[c] != null)
                                    stack.addSlice(null, channels[c]);
                            }
                        }
                        else
                            stack.addSlice(null, pixels);
                        IJ.showProgress(i, info.Length);
                    }

                    rs.Close();
                }
                catch (OutOfMemoryException e)
                {
                    IJ.outOfMemory(fi.fileName);
                    stack.deleteLastSlice();
                    stack.deleteLastSlice();
                }
                catch (Exception e)
                {
                    IJ.handleException(e);
                }
                IJ.showProgress(1.0);
                if (stack.size() == 0)
                    return null;
                if (fi.fileType == FileInfo.GRAY16_UNSIGNED || fi.fileType == FileInfo.GRAY12_UNSIGNED
                || fi.fileType == FileInfo.GRAY32_FLOAT || fi.fileType == FileInfo.RGB48)
                {
                    ImageProcessor ip = stack.getProcessor(1);
                    ip.resetMinAndMax();
                    stack.update(ip);
                }
                //if (fi.whiteIsZero)
                //	new StackProcessor(stack, stack.getProcessor(1)).invert();
                ImagePlus imp = new ImagePlus(fi.fileName, stack);

                //todo:

                // new FileOpener(fi).setCalibration(imp);
                // imp.setFileInfo(fi);
                // if (fi.info != null)
                //     imp.setProperty("Info", fi.info);
                // if (fi.description != null && fi.description.contains("order=zct"))
                //     new HyperStackConverter().shuffle(imp, HyperStackConverter.ZCT);
                // int stackSize = stack.size();
                // if (nChannels > 1 && (stackSize % nChannels) == 0)
                // {
                //     imp.setDimensions(nChannels, stackSize / nChannels, 1);
                //     imp = new CompositeImage(imp, IJ.COMPOSITE);
                //     imp.setOpenAsHyperStack(true);
                // }
                // else if (imp.getNChannels() > 1)
                //     imp = makeComposite(imp, fi);
                IJ.showProgress(1.0);
                return imp;
            }
        }

        /// <summary>
        /// Attempts to open the specified file as a tiff.
        /// Returns an ImagePlus object if successful.
        /// </summary>
        /// <param name="directory"></param>
        /// <param name="name"></param>
        /// <returns></returns>
        public ImagePlus openTiff(string directory, string name)
        {
            TiffDecoder td = new TiffDecoder(directory, name);
            //if (IJ.debugMode) td.enableDebugging();
            FileInfo[] info = null;
            try
            {
                info = td.getTiffInfo();
            }
            catch (IOException e)
            {
                this._fileType = TIFF;
                directory = PathUtil.AddSeparator(directory);
                return openUsingHandleExtraFileTypes(directory + name);
            }
            if (info == null)
                return null;
            return openTiff2(info);
        }

        /** Opens the nth image of the specified TIFF stack. */
        public ImagePlus openTiff(string path, int n)
        {
            //todo:
            throw new NotImplementedException();
            // TiffDecoder td = new TiffDecoder(getDir(path), getName(path));
            // if (IJ.debugMode) td.enableDebugging();
            // FileInfo[] info = null;
            // try
            // {
            //     info = td.getTiffInfo();
            // }
            // catch (IOException e)
            // {
            //     String msg = e.getMessage();
            //     if (msg == null || msg.equals("")) msg = "" + e;
            //     IJ.error("Open TIFF", msg);
            //     return null;
            // }
            // if (info == null) return null;
            // FileInfo fi = info[0];
            // if (info.length == 1 && fi.nImages > 1)
            // {
            //     if (n < 1 || n > fi.nImages)
            //         throw new IllegalArgumentException("N out of 1-" + fi.nImages + " range");
            //     long size = fi.width * fi.height * fi.getBytesPerPixel();
            //     fi.longOffset = fi.getOffset() + (n - 1) * (size + fi.getGap());
            //     fi.offset = 0;
            //     fi.nImages = 1;
            // }
            // else
            // {
            //     if (n < 1 || n > info.length)
            //         throw new IllegalArgumentException("N out of 1-" + info.length + " range");
            //     fi.longOffset = info[n - 1].getOffset();
            //     fi.offset = 0;
            //     fi.stripOffsets = info[n - 1].stripOffsets;
            //     fi.stripLengths = info[n - 1].stripLengths;
            // }
            // FileOpener fo = new FileOpener(fi);
            // return fo.openImage();
        }
        /** Returns the FileInfo of the specified TIFF file. */
        public static FileInfo[] getTiffFileInfo(String path)
        {
            Opener o = new Opener();
            TiffDecoder td = new TiffDecoder(o.getDir(path), o.getName(path));
            if (IJ.debugMode) td.enableDebugging();
            try
            {
                return td.getTiffInfo();
            }
            catch (IOException e)
            {
                return null;
            }
        }


        private ImagePlus makeComposite(ImagePlus imp, FileInfo fi)
        {
            int c = imp.getNChannels();
            bool composite = c > 1 && fi.description != null && fi.description.IndexOf("mode=") != -1;
            if (c > 1 && (imp.getOpenAsHyperStack() || composite) && !imp.isComposite() && imp.getType() != ImagePlus.COLOR_RGB)
            {
                int mode = IJ.COLOR;
                if (fi.description != null)
                {
                    if (fi.description.IndexOf("mode=composite") != -1)
                        mode = IJ.COMPOSITE;
                    else if (fi.description.IndexOf("mode=gray") != -1)
                        mode = IJ.GRAYSCALE;
                }
                imp = new CompositeImage(imp, mode);
            }
            return imp;
        }

        public string getName(string path)
        {
            int i = path.LastIndexOf('/');
            if (i==-1)
                i = path.LastIndexOf('\\');
            if (i>0)
                return path.Substring(i+1);
            else
                return path;
        }

        public string getDir(string path)
        {
            int i = path.LastIndexOf('/');
            if (i==-1)
                i = path.LastIndexOf('\\');
            if (i>0)
                return path.Substring(0, i+1);
            else
                return "";
        }

        ImagePlus openTiff2(FileInfo[] info)
        {
            if (info == null)
                return null;
            ImagePlus imp = null;
            // if (IJ.debugMode) // dump tiff tags
            //     IJ.log(info[0].debugInfo);
            if (info.Length > 1)
            { // try to open as stack
                imp = openTiffStack(info);
                if (imp != null)
                    return imp;
            }
            FileOpener fo = new FileOpener(info[0]);
            imp = fo.openImage();
            if (imp == null)
                return null;
            int[] offsets = info[0].stripOffsets;
            if (offsets != null && offsets.Length > 1)
            {
                long firstOffset = (long)offsets[0] & 0xffffffffL;
                long lastOffset = (long)offsets[offsets.Length - 1] & 0xffffffffL;
                if (lastOffset < firstOffset)
                    IJ.run(imp, "Flip Vertically", "stack");
            }
            imp = makeComposite(imp, info[0]);
            if (imp.getBitDepth() == 32 && imp.getTitle().StartsWith("FFT of"))
                return openFFT(imp);
            else
                return imp;
        }

        private ImagePlus openFFT(ImagePlus imp)
        {
            throw new NotImplementedException();
            // ImageProcessor ip = imp.getProcessor();
            // FHT fht = new FHT(ip, true);
            // ImageProcessor ps = fht.getPowerSpectrum();
            // ImagePlus imp2 = new ImagePlus(imp.getTitle(), ps);
            // imp2.setProperty("FHT", fht);
            // imp2.setProperty("Info", imp.getInfoProperty());
            // fht.originalWidth = (int)imp2.getNumericProperty("width");
            // fht.originalHeight = (int)imp2.getNumericProperty("height");
            // fht.originalBitDepth = (int)imp2.getNumericProperty("bitdepth");
            // fht.originalColorModel = ip.getColorModel();
            // imp2.setCalibration(imp.getCalibration());
            // return imp2;
        }

        /** Attempts to open the specified ROI, returning null if unsuccessful. */
        public Roi openRoi(string path)
        {
            Roi roi = null;
            RoiDecoder rd = new RoiDecoder(path);
            try { roi = rd.getRoi(); }
            catch (IOException e)
            {
                IJ.error("RoiDecoder", e.Message);
                return null;
            }
            return roi;
        }

        /** Used by open() and IJ.open() to open text URLs. */
        void OpenTextURL(String url)
        {
            //todo:
        }

        public ImagePlus openWithHandleExtraFileTypes(String path, int[] fileTypes)
        {
            //todo:
            return null;
            // ImagePlus imp = null;
            // if (path.EndsWith(".db"))
            // {
            //     // skip hidden Thumbs.db files on Windows
            //     fileTypes[0] = CUSTOM;
            //     return null;
            // }
            // try
            // {
            //     imp = (ImagePlus)IJ.runPlugIn("HandleExtraFileTypes", path);
            // }
            // catch (Exception e)
            // {
            //     if (IJ.debugMode) IJ.log("openWithHandleExtraFileTypes:\n" + path + "\n" + e);
            //     imp = null;
            // }
            // if (imp == null)
            //     return null;
            // FileInfo fi = imp.getOriginalFileInfo();
            // if (fi == null)
            // {
            //     fi = new FileInfo();
            //     fi.width = imp.getWidth();
            //     fi.height = imp.getHeight();
            //     fi.directory = getDir(path);
            //     fi.fileName = getName(path);
            //     imp.setFileInfo(fi);
            // }
            // if (imp.getWidth() > 0 && imp.getHeight() > 0)
            // {
            //     fileTypes[0] = CUSTOM;
            //     return imp;
            // }
            // else
            // {
            //     if (imp.getWidth() == -1)
            //         fileTypes[0] = CUSTOM; // plugin opened image so don't display error
            //     return null;
            // }
        }

        /**
        Attempts to determine the image file type by looking for
        'magic numbers' and the file name extension.
         */
        public int getFileType(string path)
        {
            if (_openUsingPlugins && !path.EndsWith(".txt") && !path.EndsWith(".java"))
                return UNKNOWN;

            string name = PathUtil.GetName(path);
            byte[] buf = new byte[132];
            try
            {
                using FileStream fs = new FileStream(path, FileMode.Open);
                using BinaryReader binaryReader = new BinaryReader(fs);
                
                binaryReader.Read(buf, 0, 132);
            }
            catch (IOException e)
            {
                return UNKNOWN;
            }

            int b0 = buf[0] & 255, b1 = buf[1] & 255, b2 = buf[2] & 255, b3 = buf[3] & 255;
            //IJ.log("getFileType: "+ name+" "+b0+" "+b1+" "+b2+" "+b3);

            // Combined TIFF and DICOM created by GE Senographe scanners
            if (buf[128] == 68 && buf[129] == 73 && buf[130] == 67 && buf[131] == 77
            && ((b0 == 73 && b1 == 73) || (b0 == 77 && b1 == 77)))
                return TIFF_AND_DICOM;

            // Big-endian TIFF ("MM")
            if (name.EndsWith(".lsm"))
                return UNKNOWN; // The LSM	Reader plugin opens these files
            if (b0 == 73 && b1 == 73 && b2 == 42 && b3 == 0 && !(_bioformats && name.EndsWith(".flex")))
                return TIFF;

            // Little-endian TIFF ("II")
            if (b0 == 77 && b1 == 77 && b2 == 0 && b3 == 42)
                return TIFF;

            // JPEG
            if (b0 == 255 && b1 == 216 && b2 == 255)
                return JPEG;

            // GIF ("GIF8")
            if (b0 == 71 && b1 == 73 && b2 == 70 && b3 == 56)
                return GIF;

            // name = name.toLowerCase(Locale.US);
            name = name.ToLower();

            // DICOM ("DICM" at offset 128)
            if (buf[128] == 68 && buf[129] == 73 && buf[130] == 67 && buf[131] == 77 || name.EndsWith(".dcm"))
            {
                return DICOM;
            }

            // ACR/NEMA with first tag = 00002,00xx or 00008,00xx
            if ((b0 == 8 || b0 == 2) && b1 == 0 && b3 == 0 && !name.EndsWith(".spe") && !name.Equals("fid"))
                return DICOM;

            // PGM ("P1", "P4", "P2", "P5", "P3" or "P6")
            if (b0 == 80 && (b1 == 49 || b1 == 52 || b1 == 50 || b1 == 53 || b1 == 51 || b1 == 54) && (b2 == 10 || b2 == 13 || b2 == 32 || b2 == 9))
                return PGM;

            // Lookup table
            if (name.EndsWith(".lut"))
                return LUT;

            // PNG
            if (b0 == 137 && b1 == 80 && b2 == 78 && b3 == 71)
                return PNG;

            // ZIP containing a TIFF
            if (name.EndsWith(".zip"))
                return ZIP;

            // FITS ("SIMP")
            if ((b0 == 83 && b1 == 73 && b2 == 77 && b3 == 80) || name.EndsWith(".fts.gz") || name.EndsWith(".fits.gz"))
                return FITS;

            // Java source file, text file or macro
            if (name.EndsWith(".java") || name.EndsWith(".txt") || name.EndsWith(".ijm") || name.EndsWith(".js")
                || name.EndsWith(".bsh") || name.EndsWith(".py") || name.EndsWith(".html"))
                return JAVA_OR_TEXT;

            // ImageJ, NIH Image, Scion Image for Windows ROI
            if (b0 == 73 && b1 == 111) // "Iout"
                return ROI;

            // ObjectJ project
            if ((b0 == 'o' && b1 == 'j' && b2 == 'j' && b3 == 0) || name.EndsWith(".ojj"))
                return OJJ;

            // Results table (tab-delimited or comma-separated tabular text)
            if (name.EndsWith(".xls") || name.EndsWith(".csv") || name.EndsWith(".tsv"))
                return TABLE;

            // AVI
            if (name.EndsWith(".avi"))
                return AVI;

            // Text file
            bool isText = true;
            for (int i = 0; i < 10; i++)
            {
                int c = buf[i] & 255;
                if ((c < 32 && c != 9 && c != 10 && c != 13) || c > 126)
                {
                    isText = false;
                    break;
                }
            }
            if (isText)
                return TEXT;

            // BMP ("BM")
            if ((b0 == 66 && b1 == 77) || name.EndsWith(".dib"))
                return BMP;

            // RAW
            if (name.EndsWith(".raw") && !Prefs.skipRawDialog)
                return RAW;

            return UNKNOWN;
        }

        /** Returns an IndexColorModel for the image specified by this FileInfo. */
        ColorModel createColorModel(FileInfo fi)
        {
            if (fi.lutSize > 0)
                return new IndexColorModel(8, fi.lutSize, fi.reds, fi.greens, fi.blues);
            else
                return LookUpTable.createGrayscaleColorModel(fi.whiteIsZero);
        }

        /** Returns an InputStream for the image described by this FileInfo. */
        InputStream createInputStream(FileInfo fi){
            if (fi.inputStream!=null)
                return fi.inputStream;
            else if (fi.url!=null && !fi.url.Equals(""))
                return new URL(fi.url+fi.fileName).openStream();
            else {
                //File f = new File(fi.getFilePath());
                System.IO.FileInfo f = new System.IO.FileInfo(fi.getFilePath());
                if (!File.Exists(fi.getFilePath()) || Directory.Exists(fi.getFilePath()))
                    return null;
                else {
                    InputStream rs = new RandomAccessStream(new FileStream(fi.getFilePath(), FileMode.Open));
                    if (fi.compression>=FileInfo.LZW || (fi.stripOffsets!=null&&fi.stripOffsets.Length>1))
                        rs = new RandomAccessStream(((RandomAccessStream)rs)._src);
                    return rs;
                }
            }
        }

        void openRGB48(ImagePlus imp)
        {
            _isRGB48 = false;
            int stackSize = imp.getStackSize();
            imp.setDimensions(3, stackSize/3, 1);
            imp = new CompositeImage(imp, IJ.COMPOSITE);
            imp.Show();
        }

        /** The "Opening: path" status message is not displayed in silent mode. */
        public void setSilentMode(bool mode)
        {
            _silentMode = mode;
        }

        /** Open all images using HandleExtraFileTypes. Set from
            a macro using setOption("openUsingPlugins", true). */
        public static void setOpenUsingPlugins(bool b)
        {
            _openUsingPlugins = b;
        }

        /** Returns the state of the openUsingPlugins flag. */
        public static bool getOpenUsingPlugins()
        {
            return _openUsingPlugins;
        }
    }
}
